" File: vim-term.vim
" Author: progliker (progliker@gmail.com)
" Version: 1.0
" Copyright: Copyright (C) 2010 progliker
"            Permission is hereby granted to use and distribute this code,
"            with or without modifications, provided that this copyright
"            notice is copied with it. Like anything else that's free,
"            vim terminal.vim is provided *as is* and comes with no warranty of any
"            kind, either expressed or implied. In no event will the copyright
"            holder be liable for any damamges resulting from the use of this
"            software.
"
" Line continuation used here
let s:cpo_save = &cpo
set cpo&vim

if !exists('loaded_vimterm')
    " First time loading the vim terminal plugin
    "
    " The vimterm plugin requires the built-in Vim system() function. If this
    " function is not available, then don't load the plugin.
    if !exists('*system')
        echomsg 'VimTerm: Vim system() built-in function is not available. ' .
                    \ 'Plugin is not loaded.'
        let loaded_vimterm = 'no'
        let &cpo = s:cpo_save
        finish
    endif

    let VimTerm_title  = '__Vim_Term__'
    let VimTerm_height = 15
    let VimTerm_prompt = 'vim_term$'
    let VimTerm_format = '^vim_term\$'

    " Define the user commands to manage the vim terminal window
    command! -nargs=0 -bar VimTermOpen call s:VimTerm_Window_Open()

    map <SID>xx <SID>xx
    let s:vterm_sid = substitute(maparg('<SID>xx'), '<SNR>\(\d\+_\)xx$',
                                \ '\1', '')
    unmap <SID>xx
    exe 'autocmd FuncUndefined *' . s:vterm_sid . 'VimTerm_* source ' .
                \ escape(expand('<sfile>'), ' ')
    exe 'autocmd FuncUndefined *' . s:vterm_sid . 'VimTerm_Window_* source ' .
                \ escape(expand('<sfile>'), ' ')

    let loaded_vimterm = 'loaded'

    " restore 'cpo'
    let &cpo = s:cpo_save
    finish
endif

if loaded_vimterm != 'loaded'
    " restore 'cpo'
    let &cpo = s:cpo_save
    finish
endif

" VimTerm plugin functionality is available
let loaded_vimterm = 'available'

" VimTerm_ExecCmd
" Execute the specified Ex command after disabling autocommands
function! s:VimTerm_ExecCmd(cmd)
    let old_eventignore = &eventignore
    set eventignore=all
    exe a:cmd
    let &eventignore = old_eventignore
endfunction

" VimTerm_Goto_Window
" Goto the vim terminal window
function! s:VimTerm_Goto_Window()
    let winnum = bufwinnr(g:VimTerm_title)
    if winnum != -1
        if winnr() != winnum
            call s:VimTerm_ExecCmd(winnum . 'wincmd w')
        endif
    endif
endfunction

" VimTerm_Window_Create
" Create a new vimterm window. If it is already open, jump to it
function! s:VimTerm_Window_Create()
    let winnum = bufwinnr(g:VimTerm_title)
    if winnum != -1
        " Jump to the existing window
        if winnr() != winnum
            exe winnum . 'wincmd w'
        endif
        return
    endif

    " Open a horizontally split window
    let win_dir = 'botright'
    " Horizontal window height
    let win_size = g:VimTerm_height

    " If the vim terminal temporary buffer already exists, then reuse it.
    " Otherwise create a new buffer
    let bufnum = bufnr(g:VimTerm_title)
    if bufnum == -1
        " Create a new buffer
        let wcmd = g:VimTerm_title
    else
        " Edit the existing buffer
        let wcmd = '+buffer' . bufnum
    endif

    " Check whether buffer is loaded
    if bufloaded(g:VimTerm_title) != 0
	let need_init = 0
    else
	let need_init = 1
    endif

    " Create the vim terminal window
    exe 'silent! ' . win_dir . ' ' . win_size . 'split ' . wcmd

    if need_init == 0
	normal! G
	startinsert!
    else
       	" Initialize the vim terminal window
	call s:VimTerm_Window_Init()
    endif
endfunction

" VimTerm_Window_Init
" Set the default options for the vim terminal window
function! s:VimTerm_Window_Init()
    " The 'readonly' option should not be set for the vim terminal buffer.
    " If Vim is started as "view/gview" or if the ":view" command is
    " used, then the 'readonly' option is set for all the buffers.
    " Unset it for the vim terminal buffer

    setlocal noreadonly

    " Define vim terminal window element highlighting
    exec 'syntax match VimTermPrompt ' . '"' . g:VimTerm_format . '"'

    " Define the highlighting only if colors are supported
    if has('gui_running') || &t_Co > 2
	highlight default link VimTermPrompt Search
    endif

    silent! setlocal nowrap

    " If the 'number' option is set in the source window, it will affect the
    " vim terminal window. So forcefully disable 'number' option for the vim terminal
    " window
    silent! setlocal nonumber

    silent! setlocal buftype=nofile
    silent! setlocal bufhidden=hide
    silent! setlocal noswapfile
    silent! setlocal nobuflisted

    " Setup the cpoptions properly for the maps to work
    let old_cpoptions = &cpoptions
    set cpoptions&vim

    " Create buffer local mappings for jumping to the tags and sorting the list
    inoremap <buffer> <silent> <CR>  <ESC>:call <SID>VimTerm_Exec()<CR>
    inoremap <buffer> <silent> <C-D> <ESC>:bdelete<CR>
    inoremap <buffer> <silent> <C-C> <ESC>:hide<CR>

    silent! put! =g:VimTerm_prompt
    2 delete
    startinsert!

    " Restore the previous cpoptions settings
    let &cpoptions = old_cpoptions
endfunction

" VimTerm_Window_Close
" Close the vim terminal window
function! s:VimTerm_Window_Close()
    " Make sure the vim terminal window exists
    let winnum = bufwinnr(g:VimTerm_title)
    if winnum == -1
        return
    endif

    if winnr() == winnum
        " Already in the vim terminal window. Close it and return
        if winbufnr(2) != -1
            " If a window other than the vim terminal window is open,
            " then only close the vim terminal window.
            close
        endif
    else
        " Goto the vim terminal window, close it and then come back to the
        " original window
        let curbufnr = bufnr('%')
        exe winnum . 'wincmd w'
        close
        " Need to jump back to the original window only if we are not
        " already in that window
        let winnum = bufwinnr(curbufnr)
        if winnr() != winnum
            exe winnum . 'wincmd w'
        endif
    endif
endfunction

" VimTerm_Window_Open
" Open and refresh the vim terminal window
function! s:VimTerm_Window_Open()
    " If the window is open, jump to it
    let winnum = bufwinnr(g:VimTerm_title)
    if winnum != -1
        " Jump to the existing window
        if winnr() != winnum
            exe winnum . 'wincmd w'
	    normal! G$
        endif
        return
    endif

    " Open the vim terminal window
    call s:VimTerm_Window_Create()
endfunction

" VimTerm_Window_Toggle()
" Open or close a vim terminal window
function! s:VimTerm_Window_Toggle()
    " If vim terminal window is open then close it.
    let winnum = bufwinnr(g:VimTerm_title)
    if winnum != -1
        call s:VimTerm_Window_Close()
        return
    endif

    call s:VimTerm_Window_Open()
endfunction

" VimTerm_Exec()
" Execute a shell command
function! s:VimTerm_Exec()
    let cmd = substitute(getline(line('.')), g:VimTerm_format . '\(.*\)$', '\1', '')
    if cmd !~ '^\s*$'
	if cmd =~ '^\s*cd\>'
	    let cmd = substitute(cmd, '^\s*\(cd.*\)$', 'l\1', '')
	    exec cmd
	else 
	    if cmd =~ '^\s*clear\>'
	       	normal! ggdG
	    else
	    	let result = system(cmd)
	       	put =result
	    endif
	endif
    endif
    put =g:VimTerm_prompt
    startinsert!
endfunction
